feat(sentinel-graph): Add Microsoft Sentinel Graph API integration (public preview)#1088
Draft
feat(sentinel-graph): Add Microsoft Sentinel Graph API integration (public preview)#1088
Conversation
Implements a new plugin for querying Microsoft Sentinel Graph API (Microsoft
Security Platform) and visualizing graph data with Graphistry.
Key Features:
- Simple API following Kusto plugin pattern: configure_sentinel_graph() + sentinel_graph(query)
- Auto-converts API responses to Graphistry nodes/edges via defensive JSON parsing
- Supports multiple authentication methods:
- Service principal (tenant_id, client_id, client_secret)
- Interactive browser credential (default)
- Device code authentication
- Custom TokenCredential
- Production-ready security hardening:
- HTTPS enforcement with HTTP endpoint rejection
- SSL certificate verification (enabled by default)
- Sanitized error messages to prevent information disclosure
- Credentials and tokens never logged
- Query content not logged (could contain sensitive filters)
- Token storage with repr=False to prevent accidental exposure
- Robust error handling:
- HTTP retry with exponential backoff
- Configurable timeout and max retries
- Token caching with 5-minute expiry buffer
- Comprehensive test coverage (30+ unit tests)
Files Added:
- graphistry/plugins/sentinel_graph.py - Main plugin implementation
- graphistry/plugins_types/sentinel_graph_types.py - Type definitions and config
- graphistry/tests/plugins/test_sentinel_graph.py - Complete test suite
- demos/demos_databases_apis/microsoft/sentinel/sentinel_graph_examples.ipynb - Demo notebook
Files Modified:
- graphistry/client_session.py - Add sentinel_graph config property
- graphistry/plotter.py - Integrate SentinelGraphMixin
- setup.py - Add 'sentinel-graph' extras dependency
Example Usage:
import graphistry
from azure.identity import InteractiveBrowserCredential
g = graphistry.configure_sentinel_graph(
graph_instance='YourGraphInstance',
credential=InteractiveBrowserCredential()
)
viz = g.sentinel_graph('MATCH (n)-[e]->(m) RETURN * LIMIT 50')
viz.plot()
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Create comprehensive test fixture module to enable testing Sentinel Graph functionality without requiring live Azure credentials or actual threat intelligence data. This improves developer experience and enables faster test iteration. **What changed:** - Created `graphistry/tests/fixtures/` package with synthetic response data - Added `sentinel_graph_responses.py` with 9 fixture functions covering: - Minimal/simple graphs for basic testing - Duplicate node scenarios for deduplication logic - Malformed JSON for error handling validation - Empty responses for edge case coverage - Complex multi-type graphs for real-world simulation - Orphan edges, special characters, and null properties - Updated `test_sentinel_graph.py` to use fixtures instead of hardcoded constants - Reformatted notebook cells (Jupyter format standardization) **Benefits:** - Tests can run without Azure credentials or Sentinel Graph instance - Fixtures mimic actual API response structure (Graph.Nodes + RawData.Rows) - Easier to add new test cases by creating additional fixtures - Validates parsing logic across diverse response scenarios - All fixtures are JSON-serializable and structure-validated **Testing:** All 9 fixtures validated successfully with proper response structure. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
The Microsoft Sentinel Graph API returns fields with sys_* prefix (sys_sourceId, sys_targetId, sys_label) instead of the underscore prefix (_sourceId, _targetId, _label) that was originally expected. - Update node/edge extraction to detect both _* and sys_* field formats - Dynamically capture all properties from nodes and edges instead of hardcoding specific fields - Normalize key fields (id, label, source, target, edge) while preserving all original properties - Add test fixture mimicking actual Sentinel Graph API response format - Add tests for sys_* field format parsing
Enable cleaner API usage without requiring bind():
graphistry.configure_sentinel_graph('instance')
graphistry.sentinel_graph(query)
Changes:
- Add GraphistryClient wrapper methods for sentinel_graph functions
- Export sentinel_graph methods at module level in pygraphistry.py
- Re-export in __init__.py for public API access
- Update docstring examples to use module-level pattern
Security: No additional risk - module-level access uses same session
model as bind() pattern. Tokens and credentials remain protected.
Microsoft moved Sentinel custom graph to public preview with a new
response schema. Updates the plugin to match:
- Rewrite response parsing for new envelope: result.graph.{nodes,edges}
and result.rawData.tables (replacing the old Graph/RawData format)
- Add responseFormats request parameter (default: ["Graph"])
- Add sentinel_graph_list() to discover available graph instances via
GET /graphs/graph-instances?graphTypes=Custom
- Remove sys_* / JSON-encoded-string field handling (pre-preview only)
- Rewrite test fixtures and tests for new schema; add TestSentinelGraphList,
TestResponseFormats, and TestTableFormatParsing test classes
- Update demo notebook with list-then-configure pattern and responseFormats example
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace substring containment with startswith host-prefix check to satisfy CodeQL 'incomplete URL substring sanitization' (alert #9). An attacker-controlled URL like https://evil.com/api.securityplatform.microsoft.com/ would still pass the old 'in url' check; the startswith form anchors the host to position 0. - Remove unused 'result =' assignment in test_execute_query_retry_on_timeout (F841 — surfaced by python-lint-types CI; assertions only check retry call counts, not the return value). Both fixes are test-only; runtime sentinel_graph.py is unchanged.
# Conflicts: # setup.py
- Add empty 'outputs' and 'execution_count' to two code cells in sentinel_graph_examples.ipynb (cells 7 and 9). nbformat 4 requires both keys on every code cell; nbsphinx errored with AttributeError: outputs during RTD's sphinx-build, killing the docs build after the pytz import was resolved upstream. - Add azure.core.* and azure.identity to mypy.ini ignore_missing_imports. Mirrors the existing azure.kusto.* entry. Without these, mypy 1.20 flags the 'azure.core.credentials' and 'azure.identity' imports in sentinel_graph.py / sentinel_graph_types.py as missing stubs, failing python-lint-types CI on every Python version. Verified locally: ruff/mypy/pytest all green; nbformat.validate passes; 'import graphistry' loads cleanly.
Return the locally-bound token (typed 'str') instead of cfg._token (typed 'Optional[str]'). Functionally equivalent — token is assigned to cfg._token on the previous line — but mypy 1.14 (the pinned mypy on the Python 3.8 lockfile) does not narrow the field-access form and flagged: 'Incompatible return value type (got str | None, expected str)'. mypy 1.20+ (3.10+ lockfiles) accepted the original code, which is why the failure was 3.8-specific. Verified clean with both mypy 1.14 and 1.20.2; sentinel-graph tests still pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a
SentinelGraphMixinplugin that integrates with the Microsoft Sentinel Graph REST API (now in public preview). Users can query their Sentinel custom graph instances using GQL and visualize the results directly in Graphistry.Features
sentinel_graph_list()— discover available instances viaGET /graphs/graph-instances?graphTypes=CustomresponseFormatsparameter — defaults to["Graph"]; pass["Table", "Graph"]to request both formats in a single callTokenCredential,DefaultAzureCredentialfallbackWhat this PR adds (new files)
graphistry/plugins/sentinel_graph.pySentinelGraphMixin— config, auth, query, list, parsegraphistry/plugins_types/sentinel_graph_types.pySentinelGraphConfigdataclass + error typesgraphistry/tests/plugins/test_sentinel_graph.pydemos/demos_databases_apis/microsoft/sentinel/sentinel_graph_examples.ipynbsetup.py[sentinel-graph]extra (azure-identity)Plus minimal wiring in
graphistry/__init__.py,graphistry/pygraphistry.py, andgraphistry/client_session.pyto exposeconfigure_sentinel_graph/sentinel_graph/sentinel_graph_list/sentinel_graph_close/sentinel_graph_from_credentialat the top-level API and per-Plotter.Notes for reviewers
result.graph.{nodes,edges}envelope). The pre-previewsys_*field format has been removed.Graphresponse format is preferred overTablefor Graphistry's use case — it gives the full connected subgraph rather than just the per-rowRETURNclause matches.@pytest.mark.integration) — they require live credentials.azure-identity. Install viapip install graphistry[sentinel-graph].Test plan
python -m pytest graphistry/tests/plugins/test_sentinel_graph.py -v— 44 unit tests pass, 1 integration test skippedsentinel_graph_list()returns correct instance metadataCI status (as of latest push)
test-gfql-core,test-minimal-python,test-docs): running for the first time after the master merge